package netfilter

/*
#cgo windows,386 LDFLAGS: -L ./lib/Release_c_api/Win32 -lnfapi
#cgo windows CFLAGS: -I ./include

#include <stdio.h>
#include <stdlib.h>
#include <winsock.h>
#define _C_API
#include "nfapi.h"

*/
import "C"

import (
	"errors"
	"fmt"
	"net"
	"net/netip"
	"syscall"
	"unsafe"

	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"gitlab.xunlei.cn/xacc/fastlogger"
	"gvisor.dev/gvisor/pkg/tcpip/header"
)

const (
	AtypIPv4        = 1
	AtypDomainName  = 3
	AtypIPv6        = 4
	AtypSend        = 0
	AtypRecv        = 1
	AtypSocks5      = 1
	AtypShadowSocks = 2
	udpBufSize      = 4096
	tcpBufSize      = 4096
	PROTOCOL_TCP    = 6
	PROTOCOL_UDP    = 17
)

var (
	ErrNotIPv4   = errors.New("no ipv4")
	ErrIsIPv6DNS = errors.New("is ipv6 dns")
)

var (
	log_udp = "nf_udp"
	log_tcp = "nf_tcp"
	log_ip  = "nf_ip"
)

//export go_threadStart
func go_threadStart() {
}

// void (NFAPI_CC *threadEnd)();
//
//export go_threadEnd
func go_threadEnd() {
}

// void (NFAPI_CC *tcpConnectRequest)(ENDPOINT_ID id, PNF_TCP_CONN_INFO pConnInfo);
//
//export go_tcpConnectRequest
func go_tcpConnectRequest(id C.ENDPOINT_ID, pConnInfo C.PNF_TCP_CONN_INFO) {
	dbgInfo := debugInfo(pConnInfo.processId, unsafe.Pointer(&(pConnInfo.localAddress)), unsafe.Pointer(&(pConnInfo.remoteAddress)))
	// localAddr, _ := getAddrPort(unsafe.Pointer(&(pConnInfo.localAddress)))
	// remoteAddr, _ := getAddrPort(unsafe.Pointer(&(pConnInfo.remoteAddress)))
	// DefaultSesions.Insert(SessionKey{Id: uint64(id)}, &Session{
	// 	id:          int64(id),
	// 	pid:         int(pConnInfo.processId),
	// 	processName: getProcessName(pConnInfo.processId),
	// 	local:       localAddr,
	// 	remote:      remoteAddr,
	// 	protocol:    PROTOCOL_TCP,
	// })
	fastlogger.Tracef("%s tcpConnectRequest %s", log_tcp, dbgInfo)
	// close tcp conn
	// C.nf_tcpClose(id)
}

// void (NFAPI_CC *tcpConnected)(ENDPOINT_ID id, PNF_TCP_CONN_INFO pConnInfo);
//
//export go_tcpConnected
func go_tcpConnected(id C.ENDPOINT_ID, pConnInfo C.PNF_TCP_CONN_INFO) {
	dbgInfo := debugInfo(pConnInfo.processId, unsafe.Pointer(&(pConnInfo.localAddress)), unsafe.Pointer(&(pConnInfo.remoteAddress)))
	fastlogger.Tracef("%s tcpConnected %s", log_tcp, dbgInfo)
}

// void (NFAPI_CC *tcpClosed)(ENDPOINT_ID id, PNF_TCP_CONN_INFO pConnInfo);
//
//export go_tcpClosed
func go_tcpClosed(id C.ENDPOINT_ID, pConnInfo C.PNF_TCP_CONN_INFO) {
	dbgInfo := debugInfo(pConnInfo.processId, unsafe.Pointer(&(pConnInfo.localAddress)), unsafe.Pointer(&(pConnInfo.remoteAddress)))
	fastlogger.Tracef("%s tcpClosed %s", log_tcp, dbgInfo)
	C.nf_tcpClose(id)
}

// void (NFAPI_CC *tcpReceive)(ENDPOINT_ID id, const char * buf, int len);
//
//export go_tcpReceive
func go_tcpReceive(id C.ENDPOINT_ID, buf *C.char, len C.int) {
	// sess, ok := DefaultSesions.Get(SessionKey{Id: uint64(id)})
	// if !ok {
	// 	fastlogger.Errorf("%s tcpReceive: session not found for id %d", log_tcp, id)
	// 	// return
	// }
	// fastlogger.Tracef("%s tcpReceive: %s -> %s", log_tcp, sess.local.String(), sess.remote.String())
	// C.nf_tcpPostReceive(id, buf, len)
}

// void (NFAPI_CC *tcpSend)(ENDPOINT_ID id, const char * buf, int len);
//
//export go_tcpSend
func go_tcpSend(id C.ENDPOINT_ID, buf *C.char, len C.int) {
	// sess, ok := DefaultSesions.Get(SessionKey{Id: uint64(id)})
	// if !ok {
	// 	fastlogger.Errorf("%s tcpSend: session not found for id %d", log_tcp, id)
	// 	// return
	// }
	// fastlogger.Tracef("%s tcpSend: %s -> %s", log_tcp, sess.local.String(), sess.remote.String())
	// C.nf_tcpPostSend(id, buf, len)
}

// void (NFAPI_CC *tcpCanReceive)(ENDPOINT_ID id);
//
//export go_tcpCanReceive
func go_tcpCanReceive(id C.ENDPOINT_ID) {
}

// void (NFAPI_CC *tcpCanSend)(ENDPOINT_ID id);
//
//export go_tcpCanSend
func go_tcpCanSend(id C.ENDPOINT_ID) {
}

// void (NFAPI_CC *udpCreated)(ENDPOINT_ID id, PNF_UDP_CONN_INFO pConnInfo);
//
//export go_udpCreated
func go_udpCreated(id C.ENDPOINT_ID, pConnInfo C.PNF_UDP_CONN_INFO) {
	// debugInfo := debugInfo(pConnInfo.processId, unsafe.Pointer(&(pConnInfo.localAddress)), nil)
	// fastlogger.Tracef("%s udpCreated %s", log_udp, debugInfo)
	// localAddr, _ := getAddrPort(unsafe.Pointer(&(pConnInfo.localAddress)))
	// DefaultSesions.Insert(SessionKey{Id: uint64(id)}, &Session{
	// 	id:          int64(id),
	// 	pid:         int(pConnInfo.processId),
	// 	processName: getProcessName(pConnInfo.processId),
	// 	local:       localAddr,
	// 	remote:      netip.AddrPort{},
	// 	protocol:    PROTOCOL_UDP,
	// })
}

// void (NFAPI_CC *udpConnectRequest)(ENDPOINT_ID id, PNF_UDP_CONN_REQUEST pConnReq);
//
//export go_udpConnectRequest
func go_udpConnectRequest(id C.ENDPOINT_ID, pConnReq C.PNF_UDP_CONN_REQUEST) {
	// Fired when add NF_INDICATE_CONNECT_REQUESTS on UDP rule, and the client call net.DialUDP, the localAddress is 0.0.0.0:0 looks useless,
	// It's not fired when the client call net.ListenUDP first then send to peers later.
	debugInfo := debugInfo(pConnReq.processId, unsafe.Pointer(&(pConnReq.localAddress)), unsafe.Pointer(&(pConnReq.remoteAddress)))
	fastlogger.Tracef("%s udpConnectRequest %s", log_udp, debugInfo)
}

// void (NFAPI_CC *udpClosed)(ENDPOINT_ID id, PNF_UDP_CONN_INFO pConnInfo);
//
//export go_udpClosed
func go_udpClosed(id C.ENDPOINT_ID, pConnInfo C.PNF_UDP_CONN_INFO) {
	debugInfo := debugInfo(pConnInfo.processId, unsafe.Pointer(&(pConnInfo.localAddress)), nil)
	fastlogger.Tracef("%s udpClosed %s", log_udp, debugInfo)
	// DefaultSesions.Remove(SessionKey{Id: uint64(id)})
}

// void (NFAPI_CC *udpReceive)(ENDPOINT_ID id, const unsigned char * remoteAddress, const char * buf, int len, PNF_UDP_OPTIONS options);
//
//export go_udpReceive
func go_udpReceive(id C.ENDPOINT_ID, remoteAddress *C.uchar, buf *C.char, len C.int, options C.PNF_UDP_OPTIONS) {
	// sess, ok := DefaultSesions.Get(SessionKey{Id: uint64(id)})
	// if !ok {
	// 	fastlogger.Errorf("%s udpReceive: session not found for id %d", log_udp, id)
	// 	C.nf_udpPostReceive(id, remoteAddress, buf, len, options)
	// 	return
	// }
	// fastlogger.Tracef("%s udpReceive: %s -> %s len: %d data: %s", log_udp, sess.local.String(), sess.remote.String(), len, string((*[1 << 30]byte)(unsafe.Pointer(buf))[:int(len)]))
	C.nf_udpPostReceive(id, remoteAddress, buf, len, options)
}

// void (NFAPI_CC *udpSend)(ENDPOINT_ID id, const unsigned char * remoteAddress, const char * buf, int len, PNF_UDP_OPTIONS options);
//
//export go_udpSend
func go_udpSend(id C.ENDPOINT_ID, remoteAddress *C.uchar, buf *C.char, blen C.int, options C.PNF_UDP_OPTIONS) {
	remote, err := getAddrPort(unsafe.Pointer(remoteAddress))
	if err != nil {
		C.nf_udpPostSend(id, remoteAddress, buf, blen, options)
		return
	}
	abuf := (*[1 << 30]byte)(unsafe.Pointer(buf))[:int(blen)]
	var BUFF = make([]byte, int(blen))
	copy(BUFF[:], abuf[:])
	fastlogger.Tracef("%s udpSend: %s len:%d", log_udp, remote.String(), blen)

	C.nf_udpPostSend(id, remoteAddress, buf, blen, options)
}

// void (NFAPI_CC *udpCanReceive)(ENDPOINT_ID id);
//
//export go_udpCanReceive
func go_udpCanReceive(id C.ENDPOINT_ID) {
}

// void (NFAPI_CC *udpCanSend)(ENDPOINT_ID id);
//
//export go_udpCanSend
func go_udpCanSend(id C.ENDPOINT_ID) {
}

// void ipReceive(const char * buf, int len, PNF_IP_PACKET_OPTIONS options);
//
//export go_ipReceive
func go_ipReceive(
	buf *C.char,
	blen C.int,
	options C.PNF_IP_PACKET_OPTIONS,
	compartmentId C.ulong,
	interfaceIndex C.ulong,
	subInterfaceIndex C.ulong,
	flags C.ulong,
) {
	// 网卡 -> 协议栈
	abuf := (*[1 << 30]byte)(unsafe.Pointer(buf))[:int(blen)]
	var BUFF = make([]byte, int(blen))
	copy(BUFF[:], abuf[:])

	// // 解析并打印数据包信息
	// ParsePacket(BUFF, "RECV")
	// fastlogger.Debugf("go_ipReceive compartmentId:%d interfaceIndex:%d subInterfaceIndex:%d flags:%d", compartmentId, interfaceIndex, subInterfaceIndex, flags)

	// direct
	C.nf_ipPostReceive(buf, blen, options)
}

// ipSend(const char * buf, int len, PNF_IP_PACKET_OPTIONS options);
//
//export go_ipSend
func go_ipSend(
	// 应用程序 -> 网卡
	buf *C.char,
	blen C.int,
	options C.PNF_IP_PACKET_OPTIONS,
	compartmentId C.ulong,
	interfaceIndex C.ulong,
	subInterfaceIndex C.ulong,
	flags C.ulong,
) {
	abuf := (*[1 << 30]byte)(unsafe.Pointer(buf))[:int(blen)]
	var BUFF = make([]byte, int(blen))
	copy(BUFF[:], abuf[:])

	// filter
	op := NfIpPacketOptions{
		IpFamily:          uint16(options.ip_family),
		CompartmentId:     uint32(compartmentId),
		InterfaceIndex:    uint32(interfaceIndex),
		SubInterfaceIndex: uint32(subInterfaceIndex),
		Flags:             uint32(flags),
	}
	ipVersion := uint8(header.IPVersion(BUFF))
	switch DefaultFilterFnListCache.Filter(BUFF, ipVersion) {
	case NF_ACCEPT: // 协议栈处理
		C.nf_ipPostSend(buf, blen, options)
	case NF_STOLEN: // 程序处理
		DefaultClientDriverNf.IpPkgFromNetfilter(BUFF, ipVersion, op)
		return
	case NF_DROP: // 丢包
		return
	default:
		return
	}

	// 解析并打印数据包信息
	// ParsePacket(BUFF, "SEND")
	// fmt.Printf("go_ipSend compartmentId:%d interfaceIndex:%d subInterfaceIndex:%d flags:%d\n", compartmentId, interfaceIndex, subInterfaceIndex, flags)

	C.nf_ipPostSend(buf, blen, options)
}

// getAddrPort get IPv4 ip port from netfilter address.
func getAddrPort(p unsafe.Pointer) (ipport netip.AddrPort, err error) {
	var addr syscall.Sockaddr
	addr, err = (*syscall.RawSockaddrAny)(p).Sockaddr()
	if err != nil {
		return
	}
	if addrV4, ok := addr.(*syscall.SockaddrInet4); ok {
		ipport = netip.AddrPortFrom(netip.AddrFrom4(addrV4.Addr), uint16(addrV4.Port))
		return ipport, nil
	}
	// TODO: support ipv6 dns
	if addrV6, ok := addr.(*syscall.SockaddrInet6); ok && uint16(addrV6.Port) == 53 {
		err = ErrIsIPv6DNS
	} else {
		err = ErrNotIPv4
	}
	return
}

// AddrPortToSockaddr 将 netip.AddrPort 转换为 syscall.Sockaddr
func AddrPortToSockaddr(addrPort netip.AddrPort) (syscall.Sockaddr, error) {
	ip := addrPort.Addr()
	port := addrPort.Port()

	if ip.Is4() {
		var addr [4]byte
		src := ip.As4()
		copy(addr[:], src[:])
		return &syscall.SockaddrInet4{
			Port: int(port),
			Addr: addr,
		}, nil
	} else if ip.Is6() {
		var addr [16]byte
		src := ip.As16()
		copy(addr[:], src[:])
		return &syscall.SockaddrInet6{
			Port: int(port),
			Addr: addr,
		}, nil
	} else {
		return nil, fmt.Errorf("unsupported IP address type")
	}
}

// Get process name of process id.
// See nfsdk-demo-1.6.8.0/samples/SocksRedirectorWindows8/SocksRedirector.cpp
func getProcessName(processId C.ulong) string {
	if processId == 4 {
		return "system"
	}
	processName := make([]C.char, 512)
	if result := C.nf_getProcessNameA(processId, &processName[0], C.ulong(len(processName))); result != 0 {
		return C.GoString(&processName[0])
	}
	return ""
}

func debugInfo(processId C.ulong, local, remote unsafe.Pointer) string {
	strLocal := "nil"
	if local != nil {
		localAddr, err := getAddrPort(local)
		fastlogger.Tracef("get local addr %v, err %v", localAddr, err)
		if err == nil {
			strLocal = localAddr.String()
		}
	}
	strRemote := "nil"
	if remote != nil {
		remoteAddr, err := getAddrPort(remote)
		fastlogger.Tracef("get remote addr %v, err %v", remoteAddr, err)
		if err == nil {
			strRemote = remoteAddr.String()
		}
	}
	processName := getProcessName(processId)
	return fmt.Sprintf("process: %s %s -> %s", processName, strLocal, strRemote)
}

// ParsePacket 解析IP数据包并打印详细信息
func ParsePacket(data []byte, direction string) {
	packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default)

	var srcIP, dstIP net.IP
	var protocol string
	var srcPort, dstPort uint16

	// 解析IP层
	if ipLayer := packet.Layer(layers.LayerTypeIPv4); ipLayer != nil {
		ip, _ := ipLayer.(*layers.IPv4)
		srcIP = ip.SrcIP
		dstIP = ip.DstIP

		switch ip.Protocol {
		case layers.IPProtocolTCP:
			protocol = "TCP"
		case layers.IPProtocolUDP:
			protocol = "UDP"
		case layers.IPProtocolICMPv4:
			protocol = "ICMP"
		default:
			protocol = fmt.Sprintf("Protocol-%d", ip.Protocol)
		}
	}

	// 解析传输层端口
	if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
		tcp, _ := tcpLayer.(*layers.TCP)
		srcPort = uint16(tcp.SrcPort)
		dstPort = uint16(tcp.DstPort)
	} else if udpLayer := packet.Layer(layers.LayerTypeUDP); udpLayer != nil {
		udp, _ := udpLayer.(*layers.UDP)
		srcPort = uint16(udp.SrcPort)
		dstPort = uint16(udp.DstPort)
	}

	// 打印信息
	if srcPort != 0 && dstPort != 0 {
		fmt.Printf("[%s] %s: %s:%d -> %s:%d (len: %d)\n",
			direction, protocol, srcIP, srcPort, dstIP, dstPort, len(data))
	} else {
		fmt.Printf("[%s] %s: %s -> %s (len: %d)\n",
			direction, protocol, srcIP, dstIP, len(data))
	}
}
